package vip.mtdp.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import vip.mtdp.common.CommonConstant;
import vip.mtdp.common.exception.AppRTException;
import vip.mtdp.common.response.CommonResponse;
import vip.mtdp.gateway.service.TokenDTO;
import vip.mtdp.gateway.service.TokenService;
import vip.mtdp.util.StringUtil;

import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;


/**
 * @author wangguoqing
 * @Description 鉴权过滤器
 * @date 2019/12/5 10:13
 */
@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "un-auth")
public class AuthFilter implements GlobalFilter ,Ordered {

    /**header 请求头**/
    private static String HEADER_PARTY_ID = "X-Party_Id";
    private static String HEADER_USER_ID = "X-User_Id";
    /** 语言,例如 简体中文=zh-CN **/
    private static String HEADER_ACCEPT_LANGUAGE = "Accept-Language";
    private static String DEFAULT_ACCEPT_LANGUAGE = "zh-CN";


    private static String USER_ID = "userId";

    private static String PARTY_ID = "partyId";


    /**鉴权失败提示**/
    private static String AUTH_FORBIDDEN_INFO = "auth error";

    private static String CONTENT_TYPE = "application/json;charset=UTF-8";

    /** 登录uri **/
    private static String LOGIN_URI = "/user-center/login";

    @Autowired
    private AuthService authService;

    @Autowired
    private TokenService tokenService;


    /**
     * Process the Web request and (optionally) delegate to the next {@code WebFilter}
     * through the given {@link GatewayFilterChain}.
     *
     * @param exchange the current server exchange
     * @param chain    provides a way to delegate to the next filter
     * @return {@code Mono<Void>} to indicate when request processing is complete
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse response = exchange.getResponse();
        ServerHttpRequest request = exchange.getRequest();
        try {
            //request.getHeaders().toSingleValueMap() 返回的map 不能做put 操作
            Map<String,String> requestHeaderMap = new HashMap<>(request.getHeaders().toSingleValueMap());
            String uri = request.getURI().getPath();
            log.info("=====> request uri = {}",uri);

            List<String> uris = authService.getWhiteList();

            //uri白名单,登录处理
            if(!uris.isEmpty() && uris.contains(uri)){
                return whiteList(exchange,chain,uri);
            }

            String partyId = "";
            String userId = "";
            //解析token&组装header,如果token为空,返回403,提示登录
            String token = requestHeaderMap.get(CommonConstant.TOKEN_KEY);
            if(StringUtil.isBlank(token)){
                throw new AppRTException("token_is_null","token is null");
            }

            //如果partyId没有值,返回403,提示登录
            String jsonData = tokenService.decodeToken(token);
            Map<String,String> dataMap = JSON.parseObject(jsonData,Map.class);
            partyId = String.valueOf(dataMap.get(PARTY_ID));
            userId = String.valueOf(dataMap.get(USER_ID));

            if(StringUtil.isBlank(partyId)){
               throw new AppRTException("party_is_null","party is null");
            }
            //设置partyId,userId 转发header值相关服务处理
            requestHeaderMap.put(HEADER_PARTY_ID,partyId);
            requestHeaderMap.put(HEADER_USER_ID,userId);
            Consumer<HttpHeaders> headersConsumer = assembleRequestHeader(requestHeaderMap);
            //request/response header 处理
            request.mutate().headers(headersConsumer).build();
            assembleResponseHeader(response);
            // 重新组装response body
            //response = assembleResponseBody(response);
            return chain.filter(exchange.mutate().request(request).response(response).build());
        }catch (Exception e){
            log.error("=====> auth error ",e);
            CommonResponse commonResponse = new CommonResponse(String.valueOf(HttpStatus.FORBIDDEN.value()),AUTH_FORBIDDEN_INFO);
            if(e instanceof AppRTException){
                AppRTException appEx = (AppRTException)e;
                commonResponse = new CommonResponse(appEx.getCode(),appEx.getMessage());
            }
            DataBuffer dataBuffer = assembleResponseHeader(response).bufferFactory().wrap(JSON.toJSONBytes(commonResponse));
            return response.writeWith(Mono.just(dataBuffer));
        }
    }

    /**
     * Get the order value of this object.
     * <p>Higher values are interpreted as lower priority. As a consequence,
     * the object with the lowest value has the highest priority (somewhat
     * analogous to Servlet {@code load-on-startup} values).
     * <p>Same order values will result in arbitrary sort positions for the
     * affected objects.
     *
     * @return the order value
     * @see #HIGHEST_PRECEDENCE
     * @see #LOWEST_PRECEDENCE
     */
    @Override
    public int getOrder() {
        return -999;
    }

    /**
     * 组装request header
     * @param headerMap
     * @return
     */
    private Consumer<HttpHeaders> assembleRequestHeader(Map<String,String> headerMap){
        Consumer<HttpHeaders> headersConsumer = httpHeader -> {
            headerMap.forEach((k,v)->{
                httpHeader.set(k,v);
            });
            //设置 accept-language 默认值
            Object obj = headerMap.get(HEADER_ACCEPT_LANGUAGE);
            if(obj == null){
                httpHeader.set(HEADER_ACCEPT_LANGUAGE,DEFAULT_ACCEPT_LANGUAGE);
            }
        };
        return headersConsumer;
    }


    /**
     * 组装 response header
     * @param response
     * @return
     */
    private ServerHttpResponse assembleResponseHeader(ServerHttpResponse response){
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE);
        return response;
    }

    /**
     * 组装 response body
     * @param response
     * @return
     */
    private ServerHttpResponse assembleResponseBody(ServerHttpResponse response){
        //读取response数据
        //添加code,message
        ServerHttpResponseDecorator serverHttpResponseDecorator = new ServerHttpResponseDecorator(response){
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                    DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                    DataBuffer dataBuffer = dataBufferFactory.join(dataBuffers);
                    byte[] content = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(content);
                    //释放内存
                    DataBufferUtils.release(dataBuffer);
                    String jsonContent = new String(content, Charset.forName(CommonConstant.CHARACTER_UTF_8));
                    log.info("=====> jsonContent {}",jsonContent);
                    Map<String,Object> map = JSONObject.parseObject(jsonContent,Map.class);

                    CommonResponse commonResponse = new CommonResponse(String.valueOf(HttpStatus.OK.value()),HttpStatus.OK.getReasonPhrase());
                    commonResponse.setData(map);
                    //重置response数据
                    content = JSON.toJSONBytes(commonResponse);
                    return dataBufferFactory.wrap(content);
                }));
            }
        };
        return serverHttpResponseDecorator;
    }

    /**
     * 白名单请求
     * @param exchange
     * @param chain
     * @param uri
     * @return
     */
    private Mono<Void> whiteList(ServerWebExchange exchange, GatewayFilterChain chain,String uri){
        ServerHttpResponse response = exchange.getResponse();
        ServerHttpRequest request = exchange.getRequest();

        //登录逻辑处理
        if(uri.contains(LOGIN_URI)){
            //取出response 数据,格式化登录接口返回的数据,生成token
            response = login(response);
        }
        request.mutate().headers(assembleRequestHeader(request.getHeaders().toSingleValueMap()));
        return chain.filter(exchange.mutate().request(request).response(response).build());
    }

    /**
     * 登录处理
     * @param response
     * @return
     */
    private ServerHttpResponse login(ServerHttpResponse response){
        //读取response数据
        //添加code,message
        ServerHttpResponseDecorator serverHttpResponseDecorator = new ServerHttpResponseDecorator(response){
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                    DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                    DataBuffer dataBuffer = dataBufferFactory.join(dataBuffers);
                    byte[] content = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(content);
                    //释放内存
                    DataBufferUtils.release(dataBuffer);
                    String jsonContent = new String(content, Charset.forName(CommonConstant.CHARACTER_UTF_8));
                    log.info("=====> login json content = {}",jsonContent);
                    Map<String,Object> map = JSONObject.parseObject(jsonContent,Map.class);

                   //成功,生成token,TODO 可以设置有效时间
                    TokenDTO tokenDTO = new TokenDTO();
                    tokenDTO.setJsonData(jsonContent);
                    String token = tokenService.createToken(tokenDTO);
                    //将token 设置到 response header 中
                    response.getHeaders().add(CommonConstant.TOKEN_KEY,token);
                    //reponse 由每个controller 处理
//                    CommonResponse commonResponse = new CommonResponse(String.valueOf(HttpStatus.OK.value()),HttpStatus.OK.getReasonPhrase());
//                    commonResponse.setData(map);
//                    //重置response数据
//                    content = JSON.toJSONBytes(commonResponse);
                    return dataBufferFactory.wrap(content);
                }));
            }
        };
        return serverHttpResponseDecorator;
    }

}
